package com.lvhx.controller;

import com.lvhx.dao.AcctbalMapper;
import com.lvhx.dao.OrderMapper;
import com.lvhx.hook.MyTransactionHook;
import com.lvhx.pojo.Account;
import com.lvhx.pojo.Acctbal;
import com.lvhx.pojo.Order;
import com.lvhx.service.OrderService;
import com.lvhx.task.ThreadPoolJob;
import com.lvhx.utils.NestableTransactionUnit;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @auther 吕宏旭
 * @date 2020/5/12 14:11
 * http://localhost:9998/swagger-ui.html#/
 */
@Api(tags = "测试事务")
@RestController
@RequestMapping("/transcation")
public class TranscationController {
    private static Logger logger = LoggerFactory.getLogger(TranscationController.class);

    @Autowired
    private OrderService orderService;
    @Autowired
    private DataSourceTransactionManager transactionManager;
    @Autowired
    TransactionDefinition transactionDefinition;

    @Resource(name = "accountA")
    private Account accountA;
    @Resource(name = "accountB")
    private Account accountB;

    @Autowired
    private AcctbalMapper acctbalMapper;

    @Autowired
    private OrderMapper orderMapper;

    TransactionStatus transactionStatus = null;


    @ApiOperation("测试手动管理事务")
    @RequestMapping(value = {"/testInsert"}, method = RequestMethod.GET)
    public void testInsert() {
//        TransactionStatus transactionStatus_all = transactionManager.getTransaction(transactionDefinition);

        try {
            //手动开启事务
            transactionStatus = transactionManager.getTransaction(transactionDefinition);
            logger.info(transactionStatus.isNewTransaction() + "transactionStatus");
            Order order = new Order();
            order.setId("11111");
            order.setName("测试插入数据");
            order.setMessageId(String.valueOf(System.currentTimeMillis()));

            orderService.testInsert(order);

//            int a = 2/0;

            //手动提交事务
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            //手动回滚事务
            transactionManager.rollback(transactionStatus);

            TransactionStatus transactionStatus_2 = transactionManager.getTransaction(transactionDefinition);
            logger.info(transactionStatus.isNewTransaction() + "transactionStatus_2");
            Order order = new Order();
            order.setId("22222");
            order.setName("测试插入数据");
            order.setMessageId(String.valueOf(System.currentTimeMillis()));

            orderService.testInsert(order);
            transactionManager.commit(transactionStatus_2);
//            throw new RuntimeException("新增通道失败");
        }

//        transactionManager.commit(transactionStatus_all);

    }


    @ApiOperation("测试线程池")
    @RequestMapping(value = {"/testThreadPool"}, method = RequestMethod.GET)
    public void testThreadPool() throws Exception {

        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future future = executorService.submit(new ThreadPoolJob());
        // 不调用get方法，错误抛不出来
        future.get();

    }

    @ApiOperation("测试多线程转账")
    @RequestMapping(value = {"/testAccount"}, method = RequestMethod.GET)
    public String testAccount() throws Exception {


        accountA.setBalamt(accountA.getBalamt().subtract(new BigDecimal("1")));
        accountB.setBalamt(accountB.getBalamt().add(new BigDecimal("1")));

//        synchronized (accountA){
//            synchronized (accountB){
//
//                accountA.setBalamt(accountA.getBalamt().subtract(new BigDecimal("1")));
//                accountB.setBalamt(accountB.getBalamt().add(new BigDecimal("1")));
//            }
//        }

        return "" + accountA.getBalamt() + "," + accountB.getBalamt();

    }


    /**
     * 创建	Creating
     * 注册	Registering
     * 释放	Releasing
     * 提交	committing
     * 取消	deregistering
     * 关闭	closing
     *
     * @param amt
     * @return
     * @throws Exception
     */
    @ApiOperation("转账测试事务")
    @RequestMapping(value = {"/testTransaction/{amt}"}, method = RequestMethod.GET)
    @Transactional(rollbackFor = Exception.class)
    public String testTransaction(@PathVariable BigDecimal amt) throws Exception {


        Acctbal from = acctbalMapper.selectByPrimaryKey("9999980901");
        Acctbal to = acctbalMapper.selectByPrimaryKey("9999980902");

        from.setAmt(from.getAmt().subtract(amt));
        acctbalMapper.updateByPrimaryKey(from);

//        int i = 1/0;

        to.setAmt(to.getAmt().add(amt));
        acctbalMapper.updateByPrimaryKey(to);

        return "success";

    }


    @ApiOperation("测试spring事务回调钩子")
    @RequestMapping(value = {"/testHock"}, method = RequestMethod.GET)
    @Transactional(rollbackFor = Exception.class)
    public String testHock() throws Exception {

        Order order = orderMapper.selectByPrimaryKey("1");

        order.setMessageId(UUID.randomUUID().toString().substring(0, 10));

        orderMapper.updateByPrimaryKeySelective(order);

        /**
         * 添加事务回调钩子
         * SOFAMQ中事务型消息就是利用这一特性，当第一次发送消息给broker成功后，消息会被标记为待投递状态
         * 当数据库事务提交后，会触发此钩子，钩子会调用broker的API，将消息从待投递状态变为可投递状态，或者删除
         *
         * 添加钩子的就是存放在ThreadLocal中的map里
         */
        TransactionSynchronizationManager.registerSynchronization(new MyTransactionHook());

        Thread thread = Thread.currentThread();

        return null;
    }


    @ApiOperation("事务内另起新事务-手动管理事务")
    @RequestMapping(value = {"/test1/{amt}"}, method = RequestMethod.GET)
//    @Transactional(rollbackFor = Exception.class)
    public String test1(@PathVariable BigDecimal amt) throws Exception {

        try {
            //手动开启事务
            transactionStatus = transactionManager.getTransaction(transactionDefinition);

            Acctbal from = acctbalMapper.selectByPrimaryKey("9999980901");
            Acctbal to = acctbalMapper.selectByPrimaryKey("9999980902");

            from.setAmt(from.getAmt().subtract(amt));
            acctbalMapper.updateByPrimaryKey(from);

            NestableTransactionUnit.execute(status -> {
                // 这里是在新事务中执行的代码
                to.setAmt(to.getAmt().add(amt));
                acctbalMapper.updateByPrimaryKey(to);

                // 返回null表示执行成功，抛出RuntimeException会回滚这个新事务
                return null;
            });

            int i = 1 / 0;

            transactionManager.commit(transactionStatus);

        } catch (Exception e) {
            //手动回滚事务
            transactionManager.rollback(transactionStatus);
        }


        return "success";

    }


    @ApiOperation("事务内另起新事务-注解管理事务")
    @RequestMapping(value = {"/test2/{amt}"}, method = RequestMethod.GET)
    @Transactional(rollbackFor = Exception.class)
    public String test2(@PathVariable BigDecimal amt) throws Exception {

        Acctbal to = null;
        try {
            Acctbal from = acctbalMapper.selectByPrimaryKey("9999980901");
            to = acctbalMapper.selectByPrimaryKey("9999980902");

            from.setAmt(from.getAmt().subtract(amt));
            acctbalMapper.updateByPrimaryKey(from);
            int i = 1/0;
        } catch (Exception e) {
            NestableTransactionUnit.execute(status -> {
                Acctbal test = acctbalMapper.selectByPrimaryKey("9999980902");

                // 这里是在新事务中执行的代码
                test.setAmt(test.getAmt().add(amt));
                acctbalMapper.updateByPrimaryKey(test);
//            throw new RuntimeException("异常");

                // 返回null表示执行成功，抛出RuntimeException会回滚这个新事务
                return null;
            });
        }



        return "success";

    }
}




